#!/usr/bin/env bash
set -e

usage() {
    echo "usage: $0 -f <package_x86_64.deb> -f <package_aarch64.deb> -r <deb|deb-dev> [-s]"
    exit 1
}

check_program() {
  if ! command -v $1 &> /dev/null
  then
      echo "$1 is required and could not be found"
      exit
  fi
}

# Used to get comma separated list of architectures
join_arr() {
  local IFS="$1"
  shift
  echo "$*"
}

# Updates the signature of a DEB package in the local repository
#
# $1: path of the repository.
# $2: suite (eg. "stable")
# $3: path of the DEB file.
sign_deb() {
    pushd $1/$2 > /dev/null
    rm -f $(basename -- $3).asc
    gpg --detach-sign --digest-algo SHA256 --armor $(basename -- $3)
    popd > /dev/null
}

# Add a package to the local DEB repository
#
# $1: path of the repository.
# $2: suite (eg. "stable")
# $3: path of the DEB file.
add_deb() {
    cp -f $3 $1/$2
    sign_deb $1 $2 $3

    # Get package architecture from dpkg
    local arch=$(dpkg --info $3 |  awk '/Architecture/ {printf "%s", $2}')
    # Store architecture in array
    architectures+=("${arch}")
}

falco_arch_from_deb_arch() {
    case "$1" in
      "amd64")
        echo -n "x86_64"
        ;;
      "arm64")
        echo -n "aarch64"
        ;;
      *)
        echo "Wrong arch."
        exit 1
        ;;
    esac  
}

# Sign the local DEB repository
#
# $1: path of the repository
# $2: suite (eg. "stable")
sign_repo() {
    local release_dir=dists/$2
    pushd $1 > /dev/null

    # release signature - Release.gpg file
    gpg --detach-sign --digest-algo SHA256 --armor ${release_dir}/Release
    rm -f ${release_dir}/Release.gpg
    mv ${release_dir}/Release.asc ${release_dir}/Release.gpg
    
    # release signature - InRelease file
    gpg --armor --sign --clearsign --digest-algo SHA256 ${release_dir}/Release
    rm -f ${release_dir}/InRelease
    mv ${release_dir}/Release.asc ${release_dir}/InRelease

    popd > /dev/null
}

# Update the local DEB repository
#
# $1: path of the repository
# $2: suite (eg. "stable")
update_repo() {
    local component=main
    local debs_dir=$2
    local release_dir=dists/$2

    pushd $1 > /dev/null

    # packages metadata
    for arch in "${architectures[@]}"; do
      local packages_dir=${release_dir}/${component}/binary-${arch}
      mkdir -p ${packages_dir}
      truncate -s 0 ${packages_dir}/Packages
      # Find all ${arch} deb files.
      # Note that debian uses {arm64,amd64}, while 
      # Falco packages use {x86_64,aarch64}.
      find ${debs_dir} -name "falco-*-$(falco_arch_from_deb_arch ${arch}).deb" -exec apt-ftparchive packages {} \; >> ${packages_dir}/Packages
      gzip -c ${packages_dir}/Packages > ${packages_dir}/Packages.gz
      bzip2 -z -c ${packages_dir}/Packages > ${packages_dir}/Packages.bz2
    done

    # release metadata
    apt-ftparchive release \
      -o APT::FTPArchive::Release::Origin=Falco \
      -o APT::FTPArchive::Release::Label=Falco \
      -o APT::FTPArchive::Release::Suite=$2 \
      -o APT::FTPArchive::Release::Codename=$2 \
      -o APT::FTPArchive::Release::Components=${component} \
      -o APT::FTPArchive::Release::Architectures="$(join_arr , "${architectures[@]}")" \
      ${release_dir} > ${release_dir}/Release

    popd > /dev/null
}

# parse options
while getopts ":f::r::s" opt; do
    case "${opt}" in
        f )
          files+=("${OPTARG}")
          ;;
        r )
          repo="${OPTARG}"
          [[ "${repo}" == "deb" || "${repo}" == "deb-dev" ]] || usage
          ;;
        s )
          sign_all="true"
          ;;
        : )
          echo "invalid option: ${OPTARG} requires an argument" 1>&2
          exit 1
          ;;
        \?)
          echo "invalid option: ${OPTARG}" 1>&2
          exit 1
          ;;
    esac
done
shift $((OPTIND-1))

# check options
if ([ ${#files[@]} -eq 0 ] && [ -z "${sign_all}" ]) || [ -z "${repo}" ]; then
    usage
fi

# check prerequisites
check_program apt-ftparchive
check_program gzip
check_program bzip2
check_program gpg
check_program aws
check_program dpkg

# settings
debSuite=stable
s3_bucket_repo="s3://falco-distribution/packages/${repo}"
cloudfront_path="/packages/${repo}"
tmp_repo_path=/tmp/falco-$repo

# prepare repository local copy
echo "Fetching ${s3_bucket_repo}..."
mkdir -p ${tmp_repo_path}
aws s3 cp ${s3_bucket_repo} ${tmp_repo_path} --recursive

# update signatures for all existing packages
if [ "${sign_all}" ]; then
  for file in ${tmp_repo_path}/${debSuite}/*; do
    if [ -f "$file" ]; then # exclude directories, symlinks, etc...
      if [[ ! $file == *.asc ]]; then # exclude signature files
        package=$(basename -- ${file})
        echo "Signing ${package}..."
        sign_deb ${tmp_repo_path} ${debSuite} ${file}

        echo "Syncing ${package}.asc to ${s3_bucket_repo}..."
        aws s3 cp ${tmp_repo_path}/${debSuite}/${package}.asc ${s3_bucket_repo}/${debSuite}/${package}.asc --acl public-read
      fi
    fi
  done
  aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/*.asc
  sign_repo ${tmp_repo_path} ${debSuite}
fi

# update the repo by adding new packages
if ! [ ${#files[@]} -eq 0 ]; then
  for file in "${files[@]}"; do
    echo "Adding ${file}..."
    add_deb ${tmp_repo_path} ${debSuite} ${file}
  done
  update_repo ${tmp_repo_path} ${debSuite}
  sign_repo ${tmp_repo_path} ${debSuite}

  # publish
  for file in "${files[@]}"; do
    package=$(basename -- ${file})
    echo "Publishing ${package} to ${s3_bucket_repo}..."
    aws s3 cp ${tmp_repo_path}/${debSuite}/${package} ${s3_bucket_repo}/${debSuite}/${package} --acl public-read
    aws s3 cp ${tmp_repo_path}/${debSuite}/${package}.asc ${s3_bucket_repo}/${debSuite}/${package}.asc --acl public-read

    aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package}
    aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/${debSuite}/${package}.asc
  done
fi

# sync dists
aws s3 sync ${tmp_repo_path}/dists ${s3_bucket_repo}/dists --delete --acl public-read
aws cloudfront create-invalidation --distribution-id ${AWS_CLOUDFRONT_DIST_ID} --paths ${cloudfront_path}/dists/*